[go: nahoru, domu]

blob: 3df061b05bb3d9d1d13e6e71c7336abdd4ca5310 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Copyright (C) 2012 Google Inc. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
// its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// @ts-nocheck
// TODO(crbug.com/1011811): Enable TypeScript compiler checks
import {drawGridLabels} from './css_grid_label_helpers.js';
import {applyMatrixToPoint, buildPath, emptyBounds} from './highlight_common.js';
const DEFAULT_EXTENDED_LINE_COLOR = 'rgba(128, 128, 128, 0.3)';
// TODO(alexrudenko): Grid label unit tests depend on this style so it cannot be extracted yet.
export const gridStyle = `
/* Grid row and column labels */
.grid-label-content {
position: absolute;
-webkit-user-select: none;
padding: 2px;
font-family: Menlo, monospace;
font-size: 10px;
min-width: 17px;
min-height: 15px;
border-radius: 2px;
box-sizing: border-box;
z-index: 1;
background-clip: padding-box;
pointer-events: none;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
.grid-label-content[data-direction=row] {
background-color: var(--row-label-color, #1A73E8);
color: var(--row-label-text-color, #FFFFFF);
}
.grid-label-content[data-direction=column] {
background-color: var(--column-label-color, #1A73E8);
color: var(--column-label-text-color, #FFFFFF);
}
.line-names ul,
.line-names .line-name {
margin: 0;
padding: 0;
list-style: none;
}
.line-names .line-name {
max-width: 100px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.line-names .grid-label-content,
.line-numbers .grid-label-content,
.track-sizes .grid-label-content {
border: 1px solid white;
--inner-corner-avoid-distance: 15px;
}
.grid-label-content.top-left.inner-shared-corner,
.grid-label-content.top-right.inner-shared-corner {
transform: translateY(var(--inner-corner-avoid-distance));
}
.grid-label-content.bottom-left.inner-shared-corner,
.grid-label-content.bottom-right.inner-shared-corner {
transform: translateY(calc(var(--inner-corner-avoid-distance) * -1));
}
.grid-label-content.left-top.inner-shared-corner,
.grid-label-content.left-bottom.inner-shared-corner {
transform: translateX(var(--inner-corner-avoid-distance));
}
.grid-label-content.right-top.inner-shared-corner,
.grid-label-content.right-bottom.inner-shared-corner {
transform: translateX(calc(var(--inner-corner-avoid-distance) * -1));
}
.line-names .grid-label-content::before,
.line-numbers .grid-label-content::before,
.track-sizes .grid-label-content::before {
position: absolute;
z-index: 1;
pointer-events: none;
content: "";
width: 3px;
height: 3px;
border: 1px solid white;
border-width: 0 1px 1px 0;
}
.line-names .grid-label-content[data-direction=row]::before,
.line-numbers .grid-label-content[data-direction=row]::before,
.track-sizes .grid-label-content[data-direction=row]::before {
background: var(--row-label-color, #1A73E8);
}
.line-names .grid-label-content[data-direction=column]::before,
.line-numbers .grid-label-content[data-direction=column]::before,
.track-sizes .grid-label-content[data-direction=column]::before {
background: var(--column-label-color, #1A73E8);
}
.grid-label-content.bottom-mid::before {
transform: translateY(-1px) rotate(45deg);
top: 100%;
}
.grid-label-content.top-mid::before {
transform: translateY(-3px) rotate(-135deg);
top: 0%;
}
.grid-label-content.left-mid::before {
transform: translateX(-3px) rotate(135deg);
left: 0%
}
.grid-label-content.right-mid::before {
transform: translateX(3px) rotate(-45deg);
right: 0%;
}
.grid-label-content.right-top::before {
transform: translateX(3px) translateY(-1px) rotate(-90deg) skewY(30deg);
right: 0%;
top: 0%;
}
.grid-label-content.right-bottom::before {
transform: translateX(3px) translateY(-3px) skewX(30deg);
right: 0%;
top: 100%;
}
.grid-label-content.bottom-right::before {
transform: translateX(1px) translateY(-1px) skewY(30deg);
right: 0%;
top: 100%;
}
.grid-label-content.bottom-left::before {
transform: translateX(-1px) translateY(-1px) rotate(90deg) skewX(30deg);
left: 0%;
top: 100%;
}
.grid-label-content.left-top::before {
transform: translateX(-3px) translateY(-1px) rotate(180deg) skewX(30deg);
left: 0%;
top: 0%;
}
.grid-label-content.left-bottom::before {
transform: translateX(-3px) translateY(-3px) rotate(90deg) skewY(30deg);
left: 0%;
top: 100%;
}
.grid-label-content.top-right::before {
transform: translateX(1px) translateY(-3px) rotate(-90deg) skewX(30deg);
right: 0%;
top: 0%;
}
.grid-label-content.top-left::before {
transform: translateX(-1px) translateY(-3px) rotate(180deg) skewY(30deg);
left: 0%;
top: 0%;
}
@media (forced-colors: active) {
.grid-label-content {
border-color: Highlight;
background-color: Canvas;
color: Text;
forced-color-adjust: none;
}
.grid-label-content::before {
background-color: Canvas;
border-color: Highlight;
}
}`;
export function drawLayoutGridHighlight(highlight, context, deviceScaleFactor, canvasWidth, canvasHeight) {
const gridBounds = emptyBounds();
const gridPath = buildPath(highlight.gridBorder, gridBounds);
// Transform the context to match the current writing-mode.
context.save();
_applyWritingModeTransformation(highlight.writingMode, gridBounds, context);
// Draw grid background
if (highlight.gridHighlightConfig.gridBackgroundColor) {
context.fillStyle = highlight.gridHighlightConfig.gridBackgroundColor;
context.fill(gridPath);
}
// Draw Grid border
if (highlight.gridHighlightConfig.gridBorderColor) {
context.save();
context.translate(0.5, 0.5);
context.lineWidth = 0;
if (highlight.gridHighlightConfig.gridBorderDash) {
context.setLineDash([3, 3]);
}
context.strokeStyle = highlight.gridHighlightConfig.gridBorderColor;
context.stroke(gridPath);
context.restore();
}
// Draw grid lines
const rowBounds = _drawGridLines(context, highlight, 'row');
const columnBounds = _drawGridLines(context, highlight, 'column');
// Draw gaps
_drawGridGap(
context, highlight.rowGaps, highlight.gridHighlightConfig.rowGapColor,
highlight.gridHighlightConfig.rowHatchColor, highlight.rotationAngle, /* flipDirection */ true);
_drawGridGap(
context, highlight.columnGaps, highlight.gridHighlightConfig.columnGapColor,
highlight.gridHighlightConfig.columnHatchColor, highlight.rotationAngle);
// Draw named grid areas
const areaBounds = _drawGridAreas(context, highlight.areaNames, highlight.gridHighlightConfig.areaBorderColor);
// The rest of the overlay is drawn without the writing-mode transformation, but we keep the matrix to transform relevant points.
const writingModeMatrix = context.getTransform();
writingModeMatrix.scaleSelf(1 / deviceScaleFactor);
context.restore();
if (highlight.gridHighlightConfig.showGridExtensionLines) {
if (rowBounds) {
_drawExtendedGridLines(
context, rowBounds, highlight.gridHighlightConfig.rowLineDash, writingModeMatrix, canvasWidth, canvasHeight);
}
if (columnBounds) {
_drawExtendedGridLines(
context, columnBounds, highlight.gridHighlightConfig.columnLineDash, writingModeMatrix, canvasWidth,
canvasHeight);
}
}
// Draw all the labels
drawGridLabels(highlight, gridBounds, areaBounds, writingModeMatrix);
}
function _applyWritingModeTransformation(writingMode, gridBounds, context) {
if (writingMode !== 'vertical-rl' && writingMode !== 'vertical-lr') {
return;
}
const topLeft = gridBounds.allPoints[0];
const bottomLeft = gridBounds.allPoints[3];
// Move to the top-left corner to do all transformations there.
context.translate(topLeft.x, topLeft.y);
if (writingMode === 'vertical-rl') {
context.rotate(90 * Math.PI / 180);
context.translate(0, -1 * (bottomLeft.y - topLeft.y));
}
if (writingMode === 'vertical-lr') {
context.rotate(90 * Math.PI / 180);
context.scale(1, -1);
}
// Move back to the original point.
context.translate(topLeft.x * -1, topLeft.y * -1);
}
function _drawGridLines(context, highlight, direction) {
const tracks = highlight[`${direction}s`];
const color = highlight.gridHighlightConfig[`${direction}LineColor`];
const dash = highlight.gridHighlightConfig[`${direction}LineDash`];
if (!color) {
return null;
}
const bounds = emptyBounds();
const path = buildPath(tracks, bounds);
context.save();
context.translate(0.5, 0.5);
if (dash) {
context.setLineDash([3, 3]);
}
context.lineWidth = 0;
context.strokeStyle = color;
context.save();
context.stroke(path);
context.restore();
context.restore();
return bounds;
}
function _drawExtendedGridLines(context, bounds, dash, writingModeMatrix, canvasWidth, canvasHeight) {
context.save();
context.strokeStyle = DEFAULT_EXTENDED_LINE_COLOR;
context.lineWidth = 1;
context.translate(0.5, 0.5);
if (dash) {
context.setLineDash([3, 3]);
}
// A grid track path is a list of lines defined by 2 points.
// Here we're going through the list of all points 2 by 2, so we can draw the extensions at the edges of each line.
for (let i = 0; i < bounds.allPoints.length; i += 2) {
let point1 = applyMatrixToPoint(bounds.allPoints[i], writingModeMatrix);
let point2 = applyMatrixToPoint(bounds.allPoints[i + 1], writingModeMatrix);
let edgePoint1;
let edgePoint2;
if (point1.x === point2.x) {
// Special case for a vertical line.
edgePoint1 = {x: point1.x, y: 0};
edgePoint2 = {x: point1.x, y: canvasHeight};
if (point2.y < point1.y) {
[point1, point2] = [point2, point1];
}
} else if (point1.y === point2.y) {
// Special case for a horizontal line.
edgePoint1 = {x: 0, y: point1.y};
edgePoint2 = {x: canvasWidth, y: point1.y};
if (point2.x < point1.x) {
[point1, point2] = [point2, point1];
}
} else {
// When the line isn't straight, we need to do some maths.
const a = (point2.y - point1.y) / (point2.x - point1.x);
const b = (point1.y * point2.x - point2.y * point1.x) / (point2.x - point1.x);
edgePoint1 = {x: 0, y: b};
edgePoint2 = {x: canvasWidth, y: (canvasWidth * a) + b};
if (point2.x < point1.x) {
[point1, point2] = [point2, point1];
}
}
context.beginPath();
context.moveTo(edgePoint1.x, edgePoint1.y);
context.lineTo(point1.x, point1.y);
context.moveTo(point2.x, point2.y);
context.lineTo(edgePoint2.x, edgePoint2.y);
context.stroke();
}
context.restore();
}
/**
* Draw all of the named grid area paths. This does not draw the labels, as
* placing labels in and around the grid for various things is handled later.
*
* @param {CanvasRenderingContext2D} context
* @param {AreaPaths} areas
* @param {string} borderColor
* @return {AreaBounds[]} The list of area names and their associated bounds.
*/
function _drawGridAreas(context, areas, borderColor) {
if (!areas || !Object.keys(areas).length) {
return [];
}
context.save();
if (borderColor) {
context.strokeStyle = borderColor;
}
context.lineWidth = 2;
const areaBounds = [];
for (const name in areas) {
const areaCommands = areas[name];
const bounds = emptyBounds();
const path = buildPath(areaCommands, bounds);
context.stroke(path);
areaBounds.push({name, bounds});
}
context.restore();
return areaBounds;
}
function _drawGridGap(context, gapCommands, gapColor, hatchColor, rotationAngle, flipDirection) {
if (!gapColor && !hatchColor) {
return;
}
context.save();
context.translate(0.5, 0.5);
context.lineWidth = 0;
const bounds = emptyBounds();
const path = buildPath(gapCommands, bounds);
// Fill the gap background if needed.
if (gapColor) {
context.fillStyle = gapColor;
context.fill(path);
}
// And draw the hatch pattern if needed.
if (hatchColor) {
_hatchFillPath(context, path, bounds, /* delta */ 10, hatchColor, rotationAngle, flipDirection);
}
context.restore();
}
/**
* Draw line hatching at a 45 degree angle for a given
* path.
* __________
* |\ \ \ |
* | \ \ \|
* | \ \ |
* |\ \ \ |
* **********
*
* @param {CanvasRenderingContext2D} context
* @param {Path2D} path
* @param {Object} bounds
* @param {number} delta - vertical gap between hatching lines in pixels
* @param {string} color
* @param {number} rotationAngle
* @param {boolean=} flipDirection - lines are drawn from top right to bottom left
*/
function _hatchFillPath(context, path, bounds, delta, color, rotationAngle, flipDirection) {
const dx = bounds.maxX - bounds.minX;
const dy = bounds.maxY - bounds.minY;
context.rect(bounds.minX, bounds.minY, dx, dy);
context.save();
context.clip(path);
context.setLineDash([5, 3]);
const majorAxis = Math.max(dx, dy);
context.strokeStyle = color;
const centerX = bounds.minX + dx / 2;
const centerY = bounds.minY + dy / 2;
context.translate(centerX, centerY);
context.rotate(rotationAngle * Math.PI / 180);
context.translate(-centerX, -centerY);
if (flipDirection) {
for (let i = -majorAxis; i < majorAxis; i += delta) {
context.beginPath();
context.moveTo(bounds.maxX - i, bounds.minY);
context.lineTo(bounds.maxX - dy - i, bounds.maxY);
context.stroke();
}
} else {
for (let i = -majorAxis; i < majorAxis; i += delta) {
context.beginPath();
context.moveTo(i + bounds.minX, bounds.minY);
context.lineTo(dy + i + bounds.minX, bounds.maxY);
context.stroke();
}
}
context.restore();
}