diff options
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/opengl/qopenglpaintengine.cpp | 13 | ||||
-rw-r--r-- | src/gui/opengl/qtriangulatingstroker.cpp | 8 | ||||
-rw-r--r-- | src/gui/opengl/qtriangulatingstroker_p.h | 4 | ||||
-rw-r--r-- | src/gui/painting/qcosmeticstroker.cpp | 2 | ||||
-rw-r--r-- | src/gui/painting/qpaintbuffer.cpp | 2 | ||||
-rw-r--r-- | src/gui/painting/qpaintengine.cpp | 2 | ||||
-rw-r--r-- | src/gui/painting/qpaintengine_raster.cpp | 9 | ||||
-rw-r--r-- | src/gui/painting/qpaintengineex.cpp | 4 | ||||
-rw-r--r-- | src/gui/painting/qpainter.cpp | 51 | ||||
-rw-r--r-- | src/gui/painting/qpainter_p.h | 9 | ||||
-rw-r--r-- | src/gui/painting/qpdf.cpp | 9 | ||||
-rw-r--r-- | src/gui/painting/qpdf_p.h | 2 | ||||
-rw-r--r-- | src/gui/painting/qpen.cpp | 44 | ||||
-rw-r--r-- | src/gui/painting/qpen_p.h | 3 |
14 files changed, 79 insertions, 83 deletions
diff --git a/src/gui/opengl/qopenglpaintengine.cpp b/src/gui/opengl/qopenglpaintengine.cpp index e0f1fe50a6..8e967207d4 100644 --- a/src/gui/opengl/qopenglpaintengine.cpp +++ b/src/gui/opengl/qopenglpaintengine.cpp @@ -1194,7 +1194,7 @@ void QOpenGL2PaintEngineEx::stroke(const QVectorPath &path, const QPen &pen) return; QOpenGL2PaintEngineState *s = state(); - if (pen.isCosmetic() && !qt_scaleForTransform(s->transform(), 0)) { + if (qt_pen_is_cosmetic(pen, state()->renderHints) && !qt_scaleForTransform(s->transform(), 0)) { // QTriangulatingStroker class is not meant to support cosmetically sheared strokes. QPaintEngineEx::stroke(path, pen); return; @@ -1229,15 +1229,16 @@ void QOpenGL2PaintEngineExPrivate::stroke(const QVectorPath &path, const QPen &p : QRectF(0, 0, width, height)); if (penStyle == Qt::SolidLine) { - stroker.process(path, pen, clip); + stroker.process(path, pen, clip, s->renderHints); } else { // Some sort of dash - dasher.process(path, pen, clip); + dasher.process(path, pen, clip, s->renderHints); QVectorPath dashStroke(dasher.points(), dasher.elementCount(), - dasher.elementTypes()); - stroker.process(dashStroke, pen, clip); + dasher.elementTypes(), + s->renderHints); + stroker.process(dashStroke, pen, clip, s->renderHints); } if (!stroker.vertexCount()) @@ -1261,7 +1262,7 @@ void QOpenGL2PaintEngineExPrivate::stroke(const QVectorPath &path, const QPen &p ? qMax(pen.miterLimit() * width, width) : width; - if (pen.isCosmetic()) + if (qt_pen_is_cosmetic(pen, q->state()->renderHints)) extra = extra * inverseScale; QRectF bounds = path.controlPointRect().adjusted(-extra, -extra, extra, extra); diff --git a/src/gui/opengl/qtriangulatingstroker.cpp b/src/gui/opengl/qtriangulatingstroker.cpp index 0be6e12721..6c8ab7d607 100644 --- a/src/gui/opengl/qtriangulatingstroker.cpp +++ b/src/gui/opengl/qtriangulatingstroker.cpp @@ -81,7 +81,7 @@ static inline void skipDuplicatePoints(const qreal **pts, const qreal *endPts) } } -void QTriangulatingStroker::process(const QVectorPath &path, const QPen &pen, const QRectF &) +void QTriangulatingStroker::process(const QVectorPath &path, const QPen &pen, const QRectF &, QPainter::RenderHints hints) { const qreal *pts = path.points(); const QPainterPath::ElementType *types = path.elements(); @@ -95,7 +95,7 @@ void QTriangulatingStroker::process(const QVectorPath &path, const QPen &pen, co m_width = realWidth / 2; - bool cosmetic = pen.isCosmetic(); + bool cosmetic = qt_pen_is_cosmetic(pen, hints); if (cosmetic) { m_width = m_width * m_inv_scale; } @@ -519,14 +519,14 @@ QDashedStrokeProcessor::QDashedStrokeProcessor() m_dash_stroker.setCubicToHook(qdashprocessor_cubicTo); } -void QDashedStrokeProcessor::process(const QVectorPath &path, const QPen &pen, const QRectF &clip) +void QDashedStrokeProcessor::process(const QVectorPath &path, const QPen &pen, const QRectF &clip, QPainter::RenderHints hints) { const qreal *pts = path.points(); const QPainterPath::ElementType *types = path.elements(); int count = path.elementCount(); - bool cosmetic = pen.isCosmetic(); + bool cosmetic = qt_pen_is_cosmetic(pen, hints); m_points.reset(); m_types.reset(); diff --git a/src/gui/opengl/qtriangulatingstroker_p.h b/src/gui/opengl/qtriangulatingstroker_p.h index 295ddbab5a..ba21c0820f 100644 --- a/src/gui/opengl/qtriangulatingstroker_p.h +++ b/src/gui/opengl/qtriangulatingstroker_p.h @@ -55,7 +55,7 @@ class Q_GUI_EXPORT QTriangulatingStroker { public: QTriangulatingStroker() : m_vertices(0) {} - void process(const QVectorPath &path, const QPen &pen, const QRectF &clip); + void process(const QVectorPath &path, const QPen &pen, const QRectF &clip, QPainter::RenderHints hints); inline int vertexCount() const { return m_vertices.size(); } inline const float *vertices() const { return m_vertices.data(); } @@ -97,7 +97,7 @@ class Q_GUI_EXPORT QDashedStrokeProcessor public: QDashedStrokeProcessor(); - void process(const QVectorPath &path, const QPen &pen, const QRectF &clip); + void process(const QVectorPath &path, const QPen &pen, const QRectF &clip, QPainter::RenderHints hints); inline void addElement(QPainterPath::ElementType type, qreal x, qreal y) { m_points.add(x); diff --git a/src/gui/painting/qcosmeticstroker.cpp b/src/gui/painting/qcosmeticstroker.cpp index 2eab7b25c5..7fcab1f087 100644 --- a/src/gui/painting/qcosmeticstroker.cpp +++ b/src/gui/painting/qcosmeticstroker.cpp @@ -275,7 +275,7 @@ void QCosmeticStroker::setup() qreal width = state->lastPen.widthF(); if (width == 0) opacity = 256; - else if (state->lastPen.isCosmetic()) + else if (qt_pen_is_cosmetic(state->lastPen, state->renderHints)) opacity = (int) 256*width; else opacity = (int) 256*width*state->txscale; diff --git a/src/gui/painting/qpaintbuffer.cpp b/src/gui/painting/qpaintbuffer.cpp index 1e5a00e835..d7b16114b8 100644 --- a/src/gui/painting/qpaintbuffer.cpp +++ b/src/gui/painting/qpaintbuffer.cpp @@ -680,7 +680,7 @@ void QPaintBufferEngine::penChanged() } else { qreal penWidth = (pen.widthF() == 0) ? 1 : pen.widthF(); QPointF transformedWidth(penWidth, penWidth); - if (!pen.isCosmetic()) + if (!qt_pen_is_cosmetic(pen, state()->renderHints)) transformedWidth = painter()->transform().map(transformedWidth); buffer->penWidthAdjustment = transformedWidth.x() / 2.0; } diff --git a/src/gui/painting/qpaintengine.cpp b/src/gui/painting/qpaintengine.cpp index 09b1f0a8e4..339421f5e2 100644 --- a/src/gui/painting/qpaintengine.cpp +++ b/src/gui/painting/qpaintengine.cpp @@ -445,7 +445,7 @@ void QPaintEngine::drawPoints(const QPointF *points, int pointCount) p->save(); QTransform transform; - if (p->pen().isCosmetic()) { + if (qt_pen_is_cosmetic(p->pen(), p->renderHints())) { transform = p->transform(); p->setTransform(QTransform()); } diff --git a/src/gui/painting/qpaintengine_raster.cpp b/src/gui/painting/qpaintengine_raster.cpp index e0eab8dc73..1a63ced897 100644 --- a/src/gui/painting/qpaintengine_raster.cpp +++ b/src/gui/painting/qpaintengine_raster.cpp @@ -771,7 +771,7 @@ void QRasterPaintEngine::updatePen(const QPen &pen) } else if (pen_style != Qt::NoPen) { if (!d->dashStroker) d->dashStroker.reset(new QDashStroker(&d->basicStroker)); - if (pen.isCosmetic()) { + if (qt_pen_is_cosmetic(pen, s->renderHints)) { d->dashStroker->setClipRect(d->deviceRect); } else { // ### I've seen this inverted devrect multiple places now... @@ -786,10 +786,11 @@ void QRasterPaintEngine::updatePen(const QPen &pen) } ensureRasterState(); // needed because of tx_noshear... + bool cosmetic = qt_pen_is_cosmetic(pen, s->renderHints); s->flags.fast_pen = pen_style > Qt::NoPen && s->penData.blend - && ((pen.isCosmetic() && penWidth <= 1) - || (!pen.isCosmetic() && s->flags.tx_noshear && penWidth * s->txscale <= 1)); + && ((cosmetic && penWidth <= 1) + || (!cosmetic && s->flags.tx_noshear && penWidth * s->txscale <= 1)); s->flags.non_complex_pen = qpen_capStyle(s->lastPen) <= Qt::SquareCap && s->flags.tx_noshear; @@ -1610,7 +1611,7 @@ void QRasterPaintEngine::stroke(const QVectorPath &path, const QPen &pen) stroker.setLegacyRoundingEnabled(s->flags.legacy_rounding); stroker.drawPath(path); } else if (s->flags.non_complex_pen && path.shape() == QVectorPath::LinesHint) { - qreal width = s->lastPen.isCosmetic() + qreal width = qt_pen_is_cosmetic(s->lastPen, s->renderHints) ? (qpen_widthf(s->lastPen) == 0 ? 1 : qpen_widthf(s->lastPen)) : qpen_widthf(s->lastPen) * s->txscale; int dashIndex = 0; diff --git a/src/gui/painting/qpaintengineex.cpp b/src/gui/painting/qpaintengineex.cpp index dd93a58635..113cbd8a8e 100644 --- a/src/gui/painting/qpaintengineex.cpp +++ b/src/gui/painting/qpaintengineex.cpp @@ -435,7 +435,7 @@ void QPaintEngineEx::stroke(const QVectorPath &path, const QPen &pen) } if (pen.style() > Qt::SolidLine) { - if (pen.isCosmetic()) { + if (qt_pen_is_cosmetic(pen, state()->renderHints)){ d->activeStroker->setClipRect(d->exDeviceRect); } else { QRectF clipRect = state()->matrix.inverted().mapRect(QRectF(d->exDeviceRect)); @@ -462,7 +462,7 @@ void QPaintEngineEx::stroke(const QVectorPath &path, const QPen &pen) flags |= QVectorPath::CurvedShapeMask; // ### Perspective Xforms are currently not supported... - if (!pen.isCosmetic()) { + if (!qt_pen_is_cosmetic(pen, state()->renderHints)) { // We include cosmetic pens in this case to avoid having to // change the current transform. Normal transformed, // non-cosmetic pens will be transformed as part of fill diff --git a/src/gui/painting/qpainter.cpp b/src/gui/painting/qpainter.cpp index 69267b259b..090faf15aa 100644 --- a/src/gui/painting/qpainter.cpp +++ b/src/gui/painting/qpainter.cpp @@ -897,26 +897,8 @@ void QPainterPrivate::updateState(QPainterState *newState) if (!newState) { engine->state = newState; - } else if (newState->state() || engine->state!=newState) { - bool setNonCosmeticPen = (newState->renderHints & QPainter::NonCosmeticDefaultPen) - && newState->pen.widthF() == 0; - if (setNonCosmeticPen) { - // Override the default pen's cosmetic state if the - // NonCosmeticDefaultPen render hint is used. - QPen oldPen = newState->pen; - newState->pen.setWidth(1); - newState->pen.setCosmetic(false); - newState->dirtyFlags |= QPaintEngine::DirtyPen; - - updateStateImpl(newState); - - // Restore the state pen back to its default to preserve visible - // state. - newState->pen = oldPen; - } else { - updateStateImpl(newState); - } + updateStateImpl(newState); } } @@ -1417,14 +1399,13 @@ void QPainterPrivate::updateState(QPainterState *newState) indicating that the engine should use fragment programs and offscreen rendering for antialiasing. - \value NonCosmeticDefaultPen The engine should interpret pens with a width - of 0 (which otherwise enables QPen::isCosmetic()) as being a non-cosmetic - pen with a width of 1. + \value NonCosmeticDefaultPen This value is obsolete, the default for QPen + is now non-cosmetic. \value Qt4CompatiblePainting Compatibility hint telling the engine to use the same X11 based fill rules as in Qt 4, where aliased rendering is offset - by slightly less than half a pixel. Potentially useful when porting a - Qt 4 application to Qt 5. + by slightly less than half a pixel. Also will treat default constructed pens + as cosmetic. Potentially useful when porting a Qt 4 application to Qt 5. \sa renderHints(), setRenderHint(), {QPainter#Rendering Quality}{Rendering Quality}, {Concentric Circles Example} @@ -3849,13 +3830,10 @@ void QPainter::setPen(const QColor &color) return; } - if (d->state->pen.style() == Qt::SolidLine - && d->state->pen.widthF() == 0 - && d->state->pen.isSolid() - && d->state->pen.color() == color) - return; + QPen pen(color.isValid() ? color : QColor(Qt::black)); - QPen pen(color.isValid() ? color : QColor(Qt::black), 0, Qt::SolidLine); + if (d->state->pen == pen) + return; d->state->pen = pen; if (d->extended) @@ -3904,7 +3882,7 @@ void QPainter::setPen(const QPen &pen) /*! \overload - Sets the painter's pen to have the given \a style, width 0 and + Sets the painter's pen to have the given \a style, width 1 and black color. */ @@ -3916,15 +3894,12 @@ void QPainter::setPen(Qt::PenStyle style) return; } - if (d->state->pen.style() == style - && (style == Qt::NoPen || (d->state->pen.widthF() == 0 - && d->state->pen.isSolid() - && d->state->pen.color() == QColor(Qt::black)))) + QPen pen = QPen(style); + + if (d->state->pen == pen) return; - // QPen(Qt::NoPen) is to avoid creating QPenData, including its brush (from the color) - // Note that this works well as long as QPen(Qt::NoPen) returns a black, zero-width pen - d->state->pen = (style == Qt::NoPen) ? QPen(Qt::NoPen) : QPen(Qt::black, 0, style); + d->state->pen = pen; if (d->extended) d->extended->penChanged(); diff --git a/src/gui/painting/qpainter_p.h b/src/gui/painting/qpainter_p.h index 6ad1cb07cc..0e46cee4b5 100644 --- a/src/gui/painting/qpainter_p.h +++ b/src/gui/painting/qpainter_p.h @@ -241,10 +241,6 @@ public: void updateMatrix(); void updateInvMatrix(); - int rectSubtraction() const { - return state->pen.style() != Qt::NoPen && state->pen.width() == 0 ? 1 : 0; - } - void checkEmulation(); static QPainterPrivate *get(QPainter *painter) @@ -269,6 +265,11 @@ Q_GUI_EXPORT void qt_draw_helper(QPainterPrivate *p, const QPainterPath &path, Q QString qt_generate_brush_key(const QBrush &brush); +inline bool qt_pen_is_cosmetic(const QPen &pen, QPainter::RenderHints hints) +{ + return pen.isCosmetic() || (const_cast<QPen &>(pen).data_ptr()->defaultWidth && (hints & QPainter::Qt4CompatiblePainting)); +} + QT_END_NAMESPACE #endif // QPAINTER_P_H diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp index d2aa938aed..1d07d44dd8 100644 --- a/src/gui/painting/qpdf.cpp +++ b/src/gui/painting/qpdf.cpp @@ -44,6 +44,7 @@ #include <qfile.h> #include <qtemporaryfile.h> #include <private/qmath_p.h> +#include <private/qpainter_p.h> #include <qnumeric.h> #include "private/qfont_p.h" #include <qimagewriter.h> @@ -784,7 +785,7 @@ QPdf::Stroker::Stroker() basicStroker.setStrokeWidth(.1); } -void QPdf::Stroker::setPen(const QPen &pen) +void QPdf::Stroker::setPen(const QPen &pen, QPainter::RenderHints hints) { if (pen.style() == Qt::NoPen) { stroker = 0; @@ -792,7 +793,7 @@ void QPdf::Stroker::setPen(const QPen &pen) } qreal w = pen.widthF(); bool zeroWidth = w < 0.0001; - cosmeticPen = pen.isCosmetic(); + cosmeticPen = qt_pen_is_cosmetic(pen, hints); if (zeroWidth) w = .1; @@ -1198,12 +1199,14 @@ void QPdfEngine::updateState(const QPaintEngineState &state) if (flags & DirtyPen) { d->pen = state.pen(); d->hasPen = d->pen.style() != Qt::NoPen; - d->stroker.setPen(d->pen); + d->stroker.setPen(d->pen, state.renderHints()); QBrush penBrush = d->pen.brush(); bool oldSimple = d->simplePen; d->simplePen = (d->hasPen && (penBrush.style() == Qt::SolidPattern) && penBrush.isOpaque()); if (oldSimple != d->simplePen) flags |= DirtyTransform; + } else if (flags & DirtyHints) { + d->stroker.setPen(d->pen, state.renderHints()); } if (flags & DirtyBrush) { d->brush = state.brush(); diff --git a/src/gui/painting/qpdf_p.h b/src/gui/painting/qpdf_p.h index ba86803288..735c5d13e0 100644 --- a/src/gui/painting/qpdf_p.h +++ b/src/gui/painting/qpdf_p.h @@ -124,7 +124,7 @@ namespace QPdf { struct Stroker { Stroker(); - void setPen(const QPen &pen); + void setPen(const QPen &pen, QPainter::RenderHints hints); void strokePath(const QPainterPath &path); ByteStream *stream; bool first; diff --git a/src/gui/painting/qpen.cpp b/src/gui/painting/qpen.cpp index d86a5f0392..6b71c3818f 100644 --- a/src/gui/painting/qpen.cpp +++ b/src/gui/painting/qpen.cpp @@ -86,7 +86,7 @@ typedef QPenPrivate QPenData; \snippet code/src_gui_painting_qpen.cpp 1 - The default pen is a solid black brush with 0 width, square + The default pen is a solid black brush with 1 width, square cap style (Qt::SquareCap), and bevel join style (Qt::BevelJoin). In addition QPen provides the color() and setColor() @@ -230,9 +230,9 @@ typedef QPenPrivate QPenData; \internal */ inline QPenPrivate::QPenPrivate(const QBrush &_brush, qreal _width, Qt::PenStyle penStyle, - Qt::PenCapStyle _capStyle, Qt::PenJoinStyle _joinStyle) + Qt::PenCapStyle _capStyle, Qt::PenJoinStyle _joinStyle, bool _defaultWidth) : ref(1), dashOffset(0), miterLimit(2), - cosmetic(false) + cosmetic(false), defaultWidth(_defaultWidth) { width = _width; brush = _brush; @@ -261,12 +261,12 @@ public: }; Q_GLOBAL_STATIC_WITH_ARGS(QPenDataHolder, defaultPenInstance, - (Qt::black, 0, Qt::SolidLine, qpen_default_cap, qpen_default_join)) + (Qt::black, 1, Qt::SolidLine, qpen_default_cap, qpen_default_join)) Q_GLOBAL_STATIC_WITH_ARGS(QPenDataHolder, nullPenInstance, - (Qt::black, 0, Qt::NoPen, qpen_default_cap, qpen_default_join)) + (Qt::black, 1, Qt::NoPen, qpen_default_cap, qpen_default_join)) /*! - Constructs a default black solid line pen with 0 width. + Constructs a default black solid line pen with 1 width. */ QPen::QPen() @@ -276,7 +276,7 @@ QPen::QPen() } /*! - Constructs a black pen with 0 width and the given \a style. + Constructs a black pen with 1 width and the given \a style. \sa setStyle() */ @@ -287,20 +287,20 @@ QPen::QPen(Qt::PenStyle style) d = nullPenInstance()->pen; d->ref.ref(); } else { - d = new QPenData(Qt::black, 0, style, qpen_default_cap, qpen_default_join); + d = new QPenData(Qt::black, 1, style, qpen_default_cap, qpen_default_join); } } /*! - Constructs a solid line pen with 0 width and the given \a color. + Constructs a solid line pen with 1 width and the given \a color. \sa setBrush(), setColor() */ QPen::QPen(const QColor &color) { - d = new QPenData(color, 0, Qt::SolidLine, qpen_default_cap, qpen_default_join); + d = new QPenData(color, 1, Qt::SolidLine, qpen_default_cap, qpen_default_join); } @@ -315,7 +315,7 @@ QPen::QPen(const QColor &color) QPen::QPen(const QBrush &brush, qreal width, Qt::PenStyle s, Qt::PenCapStyle c, Qt::PenJoinStyle j) { - d = new QPenData(brush, width, s, c, j); + d = new QPenData(brush, width, s, c, j, false); } /*! @@ -655,12 +655,15 @@ void QPen::setWidth(int width) void QPen::setWidthF(qreal width) { - if (width < 0.f) + if (width < 0.f) { qWarning("QPen::setWidthF: Setting a pen width with a negative value is not defined"); + return; + } if (qAbs(d->width - width) < 0.00000001f) return; detach(); d->width = width; + d->defaultWidth = false; } @@ -785,8 +788,7 @@ bool QPen::isSolid() const used with. Drawing a shape with a cosmetic pen ensures that its outline will have the same thickness at different scale factors. - A zero width pen is cosmetic by default; pens with a non-zero width - are non-cosmetic. + A zero width pen is cosmetic by default. \sa setCosmetic(), widthF() */ @@ -848,7 +850,8 @@ bool QPen::operator==(const QPen &p) const || (qFuzzyCompare(pdd->dashOffset, dd->dashOffset) && pdd->dashPattern == dd->dashPattern)) && p.d->brush == d->brush - && pdd->cosmetic == dd->cosmetic); + && pdd->cosmetic == dd->cosmetic + && pdd->defaultWidth == dd->defaultWidth); } @@ -910,6 +913,8 @@ QDataStream &operator<<(QDataStream &s, const QPen &p) } if (s.version() >= 9) s << double(p.dashOffset()); + if (s.version() >= QDataStream::Qt_5_0) + s << bool(dd->defaultWidth); } return s; } @@ -935,6 +940,7 @@ QDataStream &operator>>(QDataStream &s, QPen &p) QVector<qreal> dashPattern; double dashOffset = 0; bool cosmetic = false; + bool defaultWidth = false; if (s.version() < QDataStream::Qt_4_3) { quint8 style8; s >> style8; @@ -967,6 +973,13 @@ QDataStream &operator>>(QDataStream &s, QPen &p) s >> dashOffset; } + if (s.version() >= QDataStream::Qt_5_0) { + s >> defaultWidth; + } else { + // best we can do for legacy pens + defaultWidth = qFuzzyIsNull(width); + } + p.detach(); QPenData *dd = static_cast<QPenData *>(p.d); dd->width = width; @@ -978,6 +991,7 @@ QDataStream &operator>>(QDataStream &s, QPen &p) dd->miterLimit = miterLimit; dd->dashOffset = dashOffset; dd->cosmetic = cosmetic; + dd->defaultWidth = defaultWidth; return s; } diff --git a/src/gui/painting/qpen_p.h b/src/gui/painting/qpen_p.h index 93f2532e63..4b2fcaa71d 100644 --- a/src/gui/painting/qpen_p.h +++ b/src/gui/painting/qpen_p.h @@ -60,7 +60,7 @@ QT_BEGIN_NAMESPACE class QPenPrivate { public: QPenPrivate(const QBrush &brush, qreal width, Qt::PenStyle, Qt::PenCapStyle, - Qt::PenJoinStyle _joinStyle); + Qt::PenJoinStyle _joinStyle, bool defaultWidth = true); QAtomicInt ref; qreal width; QBrush brush; @@ -71,6 +71,7 @@ public: qreal dashOffset; qreal miterLimit; uint cosmetic : 1; + uint defaultWidth : 1; // default-constructed width? used for cosmetic pen compatibility }; QT_END_NAMESPACE |