HTML5 画布是否总是必须是矩形?

2022-01-17 00:00:00 canvas javascript html5-canvas shapes

我对任何形状的画布感兴趣的原因是,它可以用贝塞尔曲线剪切图像,并使网页的文本围绕画布形状流动,即剪切图像.

The reason I'm interested in canvases having any shape is that it would then be possible to cut out images with Bezier curves and have the text of a web page flow around the canvas shape, i.e. cut-out image.

需要的是拥有自由形状的 div、SVG 和 HTML5 画布的可能性.(应用于 SVG,我知道这相当于 Flash 符号.)然后您可以想象为形状应用盒子模型(填充、边框和边距),但它不会是盒子(它将与形状平行))!

What is needed is the possibility to have a free-form shaped div, SVG and HTML5 canvas. (Applied to SVG, I understand this would be equivalent to Flash symbols.) You could then imagine applying a box model (padding, border and margin) for shapes, but it wouldn't be a box (it would be parallel to the shape)!

我想这样也有可能让文本在形状内环绕,就像在形状周围流动的文本一样.

I suppose it would also then be possible to have text that wraps inside a shape as much as text that flows around a shape.

我在这里阅读了一篇关于使用 CSS 形状创建非矩形布局"的有趣博客文章:http://sarasoueidan.com/blog/css-shapes/

I read an interesting blog post about "Creating Non-Rectangular Layouts with CSS Shapes" here: http://sarasoueidan.com/blog/css-shapes/

但它不包括形状内的文字环绕.

but it doesn't include text wrapping inside a shape.

然后,还有一个用于 Brackets 的 CSS Shapes 编辑器(代码编辑器):http://blogs.adobe.com/webplatform/2014/04/17/css-shapes-editor-in-brackets/

Then, there's also a CSS Shapes editor for Brackets (a code editor): http://blogs.adobe.com/webplatform/2014/04/17/css-shapes-editor-in-brackets/

推荐答案

听起来很简单,实际上需要很多步骤才能实现.

As simple as it may sound it actually involves quite a few steps to achieve.

大纲看起来像这样:

  • 将形状定义为多边形,即.点阵
  • 查找多边形的边界(多边形适合的区域)
  • 使用 cetronid 算法或简单地使用边界中心的蛮力方法使用填充来收缩多边形
  • 定义文本的行高并将其用作扫描行数的基础
  • 基本上使用多边形填充算法在形状内找到可以填充文本的段.这样做的步骤是:
    • 通过获取与文本扫描线以及多边形中点之间的每条线的交点(使用线交叉数学)来使用奇数/偶数扫描仪
    • 按 x 对点进行排序
    • 使用奇偶点创建分段.此段将始终位于多边形内

    换句话说:您需要实现多边形填充算法,但不是填充线条(每像素线条),而是使用线条作为文本的基础.

    In other words: you would need to implement a polygon fill algorithm but instead of filling in lines (per pixel line) you use the line as basis for the text.

    这是完全可行的;实际上,我继续在这个问题上为自己创造一个挑战,为了它的乐趣,所以我创建了一个 通用解决方案我放在 GitHub 上 在 MIT 许可下发布.

    This is fully doable; actually, I went ahead to create a challenge for myself on this problem, for the fun of it, so I created a generic solution that I put on GitHub released under MIT license.

    上面介绍的原理都实现了,并把步骤可视化:

    The principle described above are implemented, and to visualize the steps:

    定义多边形和填充 - 这里我选择只使用简单的蛮力并根据中心和填充值计算更小的多边形 - 浅灰色是原始多边形,黑色显然是收缩版本:

    Define the polygon and padding - here I chose to just use a simple brute-force and calculate a smaller polygon based on center and a padding value - the light grey is the original polygon and the black obviously the contracted version:

    这些点被定义为一个数组 [x1, y1, x2, y2, ... xn, yn] 和收缩它的代码(请参阅项目链接以获取所有这些的完整源代码部分):

    The points are defined as an array [x1, y1, x2, y2, ... xn, yn] and the code to contract it (see link to project for full source on all these parts):

    var pPoints = [],
        i = 0, x, y, a, d, dx, dy;
    
    for(; i < points.length; i += 2) {
        x = points[i];
        y = points[i+1];
        dx = x - bounds.px;
        dy = y - bounds.py;
        a = Math.atan2(dy, dx);
        d = Math.sqrt(dx*dx + dy*dy) - padding;
    
        pPoints.push(bounds.px + d * Math.cos(a),
                     bounds.py + d * Math.sin(a));
    }
    

    下一步是定义我们要扫描的行.这些行基于字体的行高:

    Next step is to define the lines we want to scan. The lines are based on line height for font:

    这很简单——只要确保起点和终点在多边形之外.

    That is simple enough - just make sure the start and end points are outside the polygon.

    我们使用奇数/偶数扫描方法并检查扫描线与多边形中所有线的交点.如果我们得到一个相交点,我们会将其存储在该线的列表中.

    We use an odd/even scan approach and check intersection of the scanline versus all lines in the polygon. If we get a intersect point we store that in a list for that line.

    检测相交线的代码是:

    function getIntersection(line1, line2) {
    
        // "unroll" the objects
        var p0x = line1.x1,
            p0y = line1.y1,
            p1x = line1.x2,
            p1y = line1.y2,
            p2x = line2.x1,
            p2y = line2.y1,
            p3x = line2.x2,
            p3y = line2.y2,
    
        // calc difference between the coords
            d1x = p1x - p0x,
            d1y = p1y - p0y,
            d2x = p3x - p2x,
            d2y = p3y - p2y,
    
        // determinator
            d = d1x * d2y - d2x * d1y,
    
            px, py,
            s, t;
    
        // if is not intersecting/is parallel then return immediately
        if (Math.abs(d) < 1e-14)
            return null;
    
        // solve x and y for intersecting point
        px = p0x - p2x;
        py = p0y - p2y;
    
        s = (d1x * py - d1y * px) / d;
        if (s >= 0 && s <= 1) {
    
            // if s was in range, calc t
            t = (d2x * py - d2y * px) / d;
            if (t >= 0 && t <= 1) {
    
                return {x: p0x + (t * d1x),
                        y: p0y + (t * d1y)}
            }
        }
        return null;
    }
    

    然后我们对每条线的点进行排序,并使用点对来创建线段——这实际上是一种多边形填充算法.结果将是:

    Then we sort the point for each line and use pairs of points to create segments - this is actually a polygon-fill algorithm. The result will be:

    构建段的代码对于这篇文章来说有点广泛,所以请查看上面链接的项目.

    The code to build segments is a bit extensive for this post so check out the project linked above.

    最后我们使用这些段来替换实际的文本.我们需要从当前文本指针扫描文本,看看有多少适合段宽度.当前的代码有些基本,并跳过了很多考虑因素,例如分词、文本基线位置等,但对于初始使用它就可以了.

    And finally we use those segments to replace with actual text. We need to scan a text from current text pointer and see how much will fit inside the segment width. The current code is somewhat basic and skips a lot of considerations such as word breaks, text base-line position and so forth, but for initial use it will do.

    放在一起的结果是:

    希望这能让您了解所涉及的步骤.

    Hope this gives an idea about the steps involved.

相关文章