Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (C) 2003, 2004, 2005, 2006, 2009 Apple Inc. All rights reserved. | 2 * Copyright (C) 2003, 2004, 2005, 2006, 2009 Apple Inc. All rights reserved. |
| 3 * Copyright (C) 2013 Google Inc. All rights reserved. | 3 * Copyright (C) 2013 Google Inc. All rights reserved. |
| 4 * | 4 * |
| 5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
| 6 * modification, are permitted provided that the following conditions | 6 * modification, are permitted provided that the following conditions |
| 7 * are met: | 7 * are met: |
| 8 * 1. Redistributions of source code must retain the above copyright | 8 * 1. Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * 2. Redistributions in binary form must reproduce the above copyright | 10 * 2. Redistributions in binary form must reproduce the above copyright |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 37 #include "platform/graphics/paint/PaintRecord.h" | 37 #include "platform/graphics/paint/PaintRecord.h" |
| 38 #include "platform/graphics/paint/PaintRecorder.h" | 38 #include "platform/graphics/paint/PaintRecorder.h" |
| 39 #include "platform/instrumentation/tracing/TraceEvent.h" | 39 #include "platform/instrumentation/tracing/TraceEvent.h" |
| 40 #include "platform/weborigin/KURL.h" | 40 #include "platform/weborigin/KURL.h" |
| 41 #include "skia/ext/platform_canvas.h" | 41 #include "skia/ext/platform_canvas.h" |
| 42 #include "third_party/skia/include/core/SkAnnotation.h" | 42 #include "third_party/skia/include/core/SkAnnotation.h" |
| 43 #include "third_party/skia/include/core/SkColorFilter.h" | 43 #include "third_party/skia/include/core/SkColorFilter.h" |
| 44 #include "third_party/skia/include/core/SkData.h" | 44 #include "third_party/skia/include/core/SkData.h" |
| 45 #include "third_party/skia/include/core/SkRRect.h" | 45 #include "third_party/skia/include/core/SkRRect.h" |
| 46 #include "third_party/skia/include/core/SkRefCnt.h" | 46 #include "third_party/skia/include/core/SkRefCnt.h" |
| 47 #include "third_party/skia/include/core/SkUnPreMultiply.h" | |
|
Stephen White
2017/03/02 15:06:26
uNit: is this still used anywhere? Maybe it could
f(malita)
2017/03/03 19:16:09
Hmm, nope, how did it sneak in there? :)
Thanks,
| |
| 48 #include "third_party/skia/include/effects/SkGradientShader.h" | |
| 47 #include "third_party/skia/include/effects/SkLumaColorFilter.h" | 49 #include "third_party/skia/include/effects/SkLumaColorFilter.h" |
| 48 #include "third_party/skia/include/effects/SkPictureImageFilter.h" | 50 #include "third_party/skia/include/effects/SkPictureImageFilter.h" |
| 49 #include "third_party/skia/include/pathops/SkPathOps.h" | 51 #include "third_party/skia/include/pathops/SkPathOps.h" |
| 50 #include "third_party/skia/include/utils/SkNullCanvas.h" | 52 #include "third_party/skia/include/utils/SkNullCanvas.h" |
| 51 #include "wtf/Assertions.h" | 53 #include "wtf/Assertions.h" |
| 52 #include "wtf/MathExtras.h" | 54 #include "wtf/MathExtras.h" |
| 53 #include <memory> | 55 #include <memory> |
| 54 | 56 |
| 55 namespace blink { | 57 namespace blink { |
| 56 | 58 |
| (...skipping 445 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 502 PaintFlags fillPaint; | 504 PaintFlags fillPaint; |
| 503 fillPaint.setColor(paint.getColor()); | 505 fillPaint.setColor(paint.getColor()); |
| 504 drawRect(r1, fillPaint); | 506 drawRect(r1, fillPaint); |
| 505 drawRect(r2, fillPaint); | 507 drawRect(r2, fillPaint); |
| 506 } | 508 } |
| 507 | 509 |
| 508 adjustLineToPixelBoundaries(p1, p2, width, penStyle); | 510 adjustLineToPixelBoundaries(p1, p2, width, penStyle); |
| 509 m_canvas->drawLine(p1.x(), p1.y(), p2.x(), p2.y(), paint); | 511 m_canvas->drawLine(p1.x(), p1.y(), p2.x(), p2.y(), paint); |
| 510 } | 512 } |
| 511 | 513 |
| 514 namespace { | |
| 515 | |
| 516 #if !OS(MACOSX) | |
| 517 | |
| 518 sk_sp<SkPicture> recordMarker(GraphicsContext::DocumentMarkerLineStyle style) { | |
| 519 SkColor color = (style == GraphicsContext::DocumentMarkerGrammarLineStyle) | |
| 520 ? SkColorSetRGB(0xC0, 0xC0, 0xC0) | |
| 521 : SK_ColorRED; | |
| 522 | |
| 523 // Record the path equivalent to this legacy pattern: | |
| 524 // X o o X o o X | |
| 525 // o X o o X o | |
| 526 | |
| 527 static const float kW = 4; | |
| 528 static const float kH = 2; | |
| 529 | |
| 530 // Adjust the phase such that f' == 0 is "pixel"-centered | |
| 531 // (for optimal rasterization at native rez). | |
| 532 SkPath path; | |
| 533 path.moveTo(kW * -3 / 8, kH * 3 / 4); | |
| 534 path.cubicTo(kW * -1 / 8, kH * 3 / 4, | |
| 535 kW * -1 / 8, kH * 1 / 4, | |
| 536 kW * 1 / 8, kH * 1 / 4); | |
| 537 path.cubicTo(kW * 3 / 8, kH * 1 / 4, | |
| 538 kW * 3 / 8, kH * 3 / 4, | |
| 539 kW * 5 / 8, kH * 3 / 4); | |
| 540 path.cubicTo(kW * 7 / 8, kH * 3 / 4, | |
| 541 kW * 7 / 8, kH * 1 / 4, | |
| 542 kW * 9 / 8, kH * 1 / 4); | |
| 543 | |
| 544 SkPaint paint; | |
| 545 paint.setAntiAlias(true); | |
| 546 paint.setColor(color); | |
| 547 paint.setStyle(SkPaint::kStroke_Style); | |
| 548 paint.setStrokeWidth(kH * 1 / 2); | |
| 549 | |
| 550 SkPictureRecorder recorder; | |
| 551 recorder.beginRecording(kW, kH); | |
| 552 recorder.getRecordingCanvas()->drawPath(path, paint); | |
| 553 | |
| 554 return recorder.finishRecordingAsPicture(); | |
| 555 } | |
| 556 | |
| 557 #else // OS(MACOSX) | |
| 558 | |
| 559 sk_sp<SkPicture> recordMarker(GraphicsContext::DocumentMarkerLineStyle style) { | |
| 560 SkColor color = (style == GraphicsContext::DocumentMarkerGrammarLineStyle) | |
| 561 ? SkColorSetRGB(0x6B, 0x6B, 0x6B) | |
| 562 : SkColorSetRGB(0xFB, 0x2D, 0x1D); | |
| 563 | |
| 564 // Match the artwork used by the Mac. | |
| 565 static const float kW = 4; | |
| 566 static const float kH = 3; | |
| 567 static const float kR = 1.5f; | |
| 568 | |
| 569 // top->bottom translucent gradient. | |
| 570 const SkColor colors[2] = { | |
| 571 SkColorSetARGB(0x48, | |
| 572 SkColorGetR(color), | |
| 573 SkColorGetG(color), | |
| 574 SkColorGetB(color)), | |
| 575 color | |
| 576 }; | |
| 577 const SkPoint pts[2] = { | |
| 578 SkPoint::Make(0, 0), | |
| 579 SkPoint::Make(0, 2 * kR) | |
| 580 }; | |
| 581 | |
| 582 SkPaint paint; | |
| 583 paint.setAntiAlias(true); | |
| 584 paint.setColor(color); | |
| 585 paint.setShader(SkGradientShader::MakeLinear(pts, | |
| 586 colors, | |
| 587 nullptr, | |
| 588 ARRAY_SIZE(colors), | |
| 589 SkShader::kClamp_TileMode)); | |
| 590 SkPictureRecorder recorder; | |
| 591 recorder.beginRecording(kW, kH); | |
| 592 recorder.getRecordingCanvas()->drawCircle(kR, kR, kR, paint); | |
| 593 | |
| 594 return recorder.finishRecordingAsPicture(); | |
| 595 } | |
| 596 | |
| 597 #endif // OS(MACOSX) | |
| 598 | |
| 599 } // anonymous ns | |
| 600 | |
| 512 void GraphicsContext::drawLineForDocumentMarker(const FloatPoint& pt, | 601 void GraphicsContext::drawLineForDocumentMarker(const FloatPoint& pt, |
| 513 float width, | 602 float width, |
| 514 DocumentMarkerLineStyle style, | 603 DocumentMarkerLineStyle style, |
| 515 float zoom) { | 604 float zoom) { |
| 516 if (contextDisabled()) | 605 if (contextDisabled()) |
| 517 return; | 606 return; |
| 518 | 607 |
| 519 // Use 2x resources for a device scale factor of 1.5 or above. | 608 DEFINE_STATIC_LOCAL(SkPicture*, spellingMarker, |
| 520 // This modifes the bitmaps used for the marker, not the overall | 609 (recordMarker(DocumentMarkerSpellingLineStyle).release())) ; |
| 521 // scaling of the marker. | 610 DEFINE_STATIC_LOCAL(SkPicture*, grammarMarker, |
| 522 int deviceScaleFactor = m_deviceScaleFactor * zoom > 1.5f ? 2 : 1; | 611 (recordMarker(DocumentMarkerGrammarLineStyle).release())); |
| 612 const auto& marker = style == DocumentMarkerSpellingLineStyle | |
| 613 ? spellingMarker | |
| 614 : grammarMarker; | |
| 523 | 615 |
| 524 // Create the pattern we'll use to draw the underline. | |
| 525 int index = style == DocumentMarkerGrammarLineStyle ? 1 : 0; | |
| 526 static SkBitmap* misspellBitmap1x[2] = {0, 0}; | |
| 527 static SkBitmap* misspellBitmap2x[2] = {0, 0}; | |
| 528 SkBitmap** misspellBitmap = | |
| 529 deviceScaleFactor == 2 ? misspellBitmap2x : misspellBitmap1x; | |
| 530 if (!misspellBitmap[index]) { | |
| 531 #if OS(MACOSX) | |
| 532 // Match the artwork used by the Mac. | |
| 533 const int rowPixels = 4 * deviceScaleFactor; | |
| 534 const int colPixels = 3 * deviceScaleFactor; | |
| 535 SkBitmap bitmap; | |
| 536 if (!bitmap.tryAllocN32Pixels(rowPixels, colPixels)) | |
| 537 return; | |
| 538 | |
| 539 bitmap.eraseARGB(0, 0, 0, 0); | |
| 540 const uint32_t transparentColor = 0x00000000; | |
| 541 | |
| 542 if (deviceScaleFactor == 1) { | |
| 543 const uint32_t colors[2][6] = {{0x2a2a0600, 0x57571000, 0xa8a81b00, | |
| 544 0xbfbf1f00, 0x70701200, 0xe0e02400}, | |
| 545 {0x2a0f0f0f, 0x571e1e1e, 0xa83d3d3d, | |
| 546 0xbf454545, 0x70282828, 0xe0515151}}; | |
| 547 | |
| 548 // Pattern: a b a a b a | |
| 549 // c d c c d c | |
| 550 // e f e e f e | |
| 551 for (int x = 0; x < colPixels; ++x) { | |
| 552 uint32_t* row = bitmap.getAddr32(0, x); | |
| 553 row[0] = colors[index][x * 2]; | |
| 554 row[1] = colors[index][x * 2 + 1]; | |
| 555 row[2] = colors[index][x * 2]; | |
| 556 row[3] = transparentColor; | |
| 557 } | |
| 558 } else if (deviceScaleFactor == 2) { | |
| 559 const uint32_t colors[2][18] = { | |
| 560 {0x0a090101, 0x33320806, 0x55540f0a, 0x37360906, 0x6e6c120c, | |
| 561 0x6e6c120c, 0x7674140d, 0x8d8b1810, 0x8d8b1810, 0x96941a11, | |
| 562 0xb3b01f15, 0xb3b01f15, 0x6d6b130c, 0xd9d62619, 0xd9d62619, | |
| 563 0x19180402, 0x7c7a150e, 0xcecb2418}, | |
| 564 {0x0a020202, 0x33141414, 0x55232323, 0x37161616, 0x6e2e2e2e, | |
| 565 0x6e2e2e2e, 0x76313131, 0x8d3a3a3a, 0x8d3a3a3a, 0x963e3e3e, | |
| 566 0xb34b4b4b, 0xb34b4b4b, 0x6d2d2d2d, 0xd95b5b5b, 0xd95b5b5b, | |
| 567 0x19090909, 0x7c343434, 0xce575757}}; | |
| 568 | |
| 569 // Pattern: a b c c b a | |
| 570 // d e f f e d | |
| 571 // g h j j h g | |
| 572 // k l m m l k | |
| 573 // n o p p o n | |
| 574 // q r s s r q | |
| 575 for (int x = 0; x < colPixels; ++x) { | |
| 576 uint32_t* row = bitmap.getAddr32(0, x); | |
| 577 row[0] = colors[index][x * 3]; | |
| 578 row[1] = colors[index][x * 3 + 1]; | |
| 579 row[2] = colors[index][x * 3 + 2]; | |
| 580 row[3] = colors[index][x * 3 + 2]; | |
| 581 row[4] = colors[index][x * 3 + 1]; | |
| 582 row[5] = colors[index][x * 3]; | |
| 583 row[6] = transparentColor; | |
| 584 row[7] = transparentColor; | |
| 585 } | |
| 586 } else | |
| 587 ASSERT_NOT_REACHED(); | |
| 588 | |
| 589 misspellBitmap[index] = new SkBitmap(bitmap); | |
| 590 #else | |
| 591 // We use a 2-pixel-high misspelling indicator because that seems to be | |
| 592 // what Blink is designed for, and how much room there is in a typical | |
| 593 // page for it. | |
| 594 const int rowPixels = | |
| 595 32 * deviceScaleFactor; // Must be multiple of 4 for pattern below. | |
| 596 const int colPixels = 2 * deviceScaleFactor; | |
| 597 SkBitmap bitmap; | |
| 598 if (!bitmap.tryAllocN32Pixels(rowPixels, colPixels)) | |
| 599 return; | |
| 600 | |
| 601 bitmap.eraseARGB(0, 0, 0, 0); | |
| 602 if (deviceScaleFactor == 1) | |
| 603 draw1xMarker(&bitmap, index); | |
| 604 else if (deviceScaleFactor == 2) | |
| 605 draw2xMarker(&bitmap, index); | |
| 606 else | |
| 607 ASSERT_NOT_REACHED(); | |
| 608 | |
| 609 misspellBitmap[index] = new SkBitmap(bitmap); | |
| 610 #endif | |
| 611 } | |
| 612 | |
| 613 #if OS(MACOSX) | |
| 614 // Position already includes zoom and device scale factor. | 616 // Position already includes zoom and device scale factor. |
| 615 SkScalar originX = WebCoreFloatToSkScalar(pt.x()); | 617 SkScalar originX = WebCoreFloatToSkScalar(pt.x()); |
| 616 SkScalar originY = WebCoreFloatToSkScalar(pt.y()); | 618 SkScalar originY = WebCoreFloatToSkScalar(pt.y()); |
| 617 | 619 |
| 620 #if OS(MACOSX) | |
| 618 // Make sure to draw only complete dots, and finish inside the marked text. | 621 // Make sure to draw only complete dots, and finish inside the marked text. |
| 619 float rowPixels = misspellBitmap[index]->width() * zoom / deviceScaleFactor; | 622 width -= fmodf(width, marker->cullRect().width() * zoom); |
| 620 float dotCount = floorf(width / rowPixels); | |
| 621 width = dotCount * rowPixels; | |
| 622 #else | 623 #else |
| 623 SkScalar originX = WebCoreFloatToSkScalar(pt.x()); | |
| 624 | |
| 625 // Offset it vertically by 1 so that there's some space under the text. | 624 // Offset it vertically by 1 so that there's some space under the text. |
| 626 SkScalar originY = WebCoreFloatToSkScalar(pt.y()) + 1; | 625 originY += 1; |
| 627 #endif | 626 #endif |
| 628 | 627 |
| 629 SkMatrix localMatrix; | 628 const auto rect = SkRect::MakeWH(width, marker->cullRect().height() * zoom); |
| 630 localMatrix.setScale(zoom / deviceScaleFactor, zoom / deviceScaleFactor); | 629 const auto localMatrix = SkMatrix::MakeScale(zoom, zoom); |
| 631 localMatrix.postTranslate(originX, originY); | |
| 632 | 630 |
| 633 PaintFlags paint; | 631 PaintFlags paint; |
| 634 paint.setShader(WrapSkShader(SkShader::MakeBitmapShader( | 632 paint.setAntiAlias(true); |
| 635 *misspellBitmap[index], SkShader::kRepeat_TileMode, | 633 paint.setShader(WrapSkShader( |
| 636 SkShader::kRepeat_TileMode, &localMatrix))); | 634 SkShader::MakePictureShader(sk_ref_sp(marker), |
| 635 SkShader::kRepeat_TileMode, | |
| 636 SkShader::kClamp_TileMode, | |
| 637 &localMatrix, | |
| 638 nullptr))); | |
| 637 | 639 |
| 638 SkRect rect; | 640 // Apply the origin translation as a global transform. This ensures that the |
| 639 rect.set( | 641 // shader local matrix depends solely on zoom => Skia can reuse the same |
| 640 originX, originY, originX + WebCoreFloatToSkScalar(width), | 642 // cached tile for all markers at a given zoom level. |
| 641 originY + | 643 SkAutoCanvasRestore acr(m_canvas, true); |
| 642 SkIntToScalar(misspellBitmap[index]->height() / deviceScaleFactor) * | 644 m_canvas->translate(originX, originY); |
| 643 zoom); | 645 m_canvas->drawRect(rect, paint); |
| 644 | |
| 645 drawRect(rect, paint); | |
| 646 } | 646 } |
| 647 | 647 |
| 648 void GraphicsContext::drawLineForText(const FloatPoint& pt, float width) { | 648 void GraphicsContext::drawLineForText(const FloatPoint& pt, float width) { |
| 649 if (contextDisabled()) | 649 if (contextDisabled()) |
| 650 return; | 650 return; |
| 651 | 651 |
| 652 if (width <= 0) | 652 if (width <= 0) |
| 653 return; | 653 return; |
| 654 | 654 |
| 655 PaintFlags paint; | 655 PaintFlags paint; |
| (...skipping 667 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1323 case ColorFilterNone: | 1323 case ColorFilterNone: |
| 1324 break; | 1324 break; |
| 1325 default: | 1325 default: |
| 1326 ASSERT_NOT_REACHED(); | 1326 ASSERT_NOT_REACHED(); |
| 1327 break; | 1327 break; |
| 1328 } | 1328 } |
| 1329 | 1329 |
| 1330 return nullptr; | 1330 return nullptr; |
| 1331 } | 1331 } |
| 1332 | 1332 |
| 1333 #if !OS(MACOSX) | |
| 1334 void GraphicsContext::draw2xMarker(SkBitmap* bitmap, int index) { | |
| 1335 const SkPMColor lineColor = lineColors(index); | |
| 1336 const SkPMColor antiColor1 = antiColors1(index); | |
| 1337 const SkPMColor antiColor2 = antiColors2(index); | |
| 1338 | |
| 1339 uint32_t* row1 = bitmap->getAddr32(0, 0); | |
| 1340 uint32_t* row2 = bitmap->getAddr32(0, 1); | |
| 1341 uint32_t* row3 = bitmap->getAddr32(0, 2); | |
| 1342 uint32_t* row4 = bitmap->getAddr32(0, 3); | |
| 1343 | |
| 1344 // Pattern: X0o o0X0o o0 | |
| 1345 // XX0o o0XXX0o o0X | |
| 1346 // o0XXX0o o0XXX0o | |
| 1347 // o0X0o o0X0o | |
| 1348 const SkPMColor row1Color[] = {lineColor, antiColor1, antiColor2, 0, | |
| 1349 0, 0, antiColor2, antiColor1}; | |
| 1350 const SkPMColor row2Color[] = {lineColor, lineColor, antiColor1, antiColor2, | |
| 1351 0, antiColor2, antiColor1, lineColor}; | |
| 1352 const SkPMColor row3Color[] = {0, antiColor2, antiColor1, lineColor, | |
| 1353 lineColor, lineColor, antiColor1, antiColor2}; | |
| 1354 const SkPMColor row4Color[] = {0, 0, antiColor2, antiColor1, | |
| 1355 lineColor, antiColor1, antiColor2, 0}; | |
| 1356 | |
| 1357 for (int x = 0; x < bitmap->width() + 8; x += 8) { | |
| 1358 int count = std::min(bitmap->width() - x, 8); | |
| 1359 if (count > 0) { | |
| 1360 memcpy(row1 + x, row1Color, count * sizeof(SkPMColor)); | |
| 1361 memcpy(row2 + x, row2Color, count * sizeof(SkPMColor)); | |
| 1362 memcpy(row3 + x, row3Color, count * sizeof(SkPMColor)); | |
| 1363 memcpy(row4 + x, row4Color, count * sizeof(SkPMColor)); | |
| 1364 } | |
| 1365 } | |
| 1366 } | |
| 1367 | |
| 1368 void GraphicsContext::draw1xMarker(SkBitmap* bitmap, int index) { | |
| 1369 const uint32_t lineColor = lineColors(index); | |
| 1370 const uint32_t antiColor = antiColors2(index); | |
| 1371 | |
| 1372 // Pattern: X o o X o o X | |
| 1373 // o X o o X o | |
| 1374 uint32_t* row1 = bitmap->getAddr32(0, 0); | |
| 1375 uint32_t* row2 = bitmap->getAddr32(0, 1); | |
| 1376 for (int x = 0; x < bitmap->width(); x++) { | |
| 1377 switch (x % 4) { | |
| 1378 case 0: | |
| 1379 row1[x] = lineColor; | |
| 1380 break; | |
| 1381 case 1: | |
| 1382 row1[x] = antiColor; | |
| 1383 row2[x] = antiColor; | |
| 1384 break; | |
| 1385 case 2: | |
| 1386 row2[x] = lineColor; | |
| 1387 break; | |
| 1388 case 3: | |
| 1389 row1[x] = antiColor; | |
| 1390 row2[x] = antiColor; | |
| 1391 break; | |
| 1392 } | |
| 1393 } | |
| 1394 } | |
| 1395 | |
| 1396 SkPMColor GraphicsContext::lineColors(int index) { | |
| 1397 static const SkPMColor colors[] = { | |
| 1398 SkPreMultiplyARGB(0xFF, 0xFF, 0x00, 0x00), // Opaque red. | |
| 1399 SkPreMultiplyARGB(0xFF, 0xC0, 0xC0, 0xC0) // Opaque gray. | |
| 1400 }; | |
| 1401 | |
| 1402 return colors[index]; | |
| 1403 } | |
| 1404 | |
| 1405 SkPMColor GraphicsContext::antiColors1(int index) { | |
| 1406 static const SkPMColor colors[] = { | |
| 1407 SkPreMultiplyARGB(0xB0, 0xFF, 0x00, 0x00), // Semitransparent red. | |
| 1408 SkPreMultiplyARGB(0xB0, 0xC0, 0xC0, 0xC0) // Semitransparent gray. | |
| 1409 }; | |
| 1410 | |
| 1411 return colors[index]; | |
| 1412 } | |
| 1413 | |
| 1414 SkPMColor GraphicsContext::antiColors2(int index) { | |
| 1415 static const SkPMColor colors[] = { | |
| 1416 SkPreMultiplyARGB(0x60, 0xFF, 0x00, 0x00), // More transparent red | |
| 1417 SkPreMultiplyARGB(0x60, 0xC0, 0xC0, 0xC0) // More transparent gray | |
| 1418 }; | |
| 1419 | |
| 1420 return colors[index]; | |
| 1421 } | |
| 1422 #endif | |
| 1423 | |
| 1424 } // namespace blink | 1333 } // namespace blink |
| OLD | NEW |